///////////////////////////////////////////////////////////////////////////////////////////////////
/// \file formic/fqmc/sampler.h
///
/// \brief   header file for the sampler class
///
///////////////////////////////////////////////////////////////////////////////////////////////////

#ifndef FORMIC_FQMC_SAMPLER_HEADER
#define FORMIC_FQMC_SAMPLER_HEADER

#include<complex>
#include<algorithm>

#include<boost/shared_ptr.hpp>

#include<formic/input/base.h>
#include<formic/fqmc/vmc_walker.h>
#include<formic/random/random.h>
#include<formic/wfn/configuration.h>
#include<formic/wfn/coeff_obj.h>
#include<formic/fqmc/accumulator.h>

namespace formic { namespace fqmc {

  template<class S> class SimpleSampler;

  template<class S> class Sampler {

    // private data members
    private:

      const formic::InputBase & _userinp; ///< reference to user input options

      formic::VMC_Walker<S> _walker; ///< the walker used in sampling

      int _sl; ///< sample length

      int _bl; ///< burn in length

      formic::Configuration _init_config; ///< the initial configuration for sampling

      formic::LaggedFibonacci<> _rng; ///< the random number generator to use for sampling

    // public member functions
    public:

      /// \brief Construct the sampler
      Sampler(const formic::InputBase & userinp,
              const int burn_len,
              const int samp_len,
              const formic::Configuration & init_config,
              const formic::LaggedFibonacci<> & rng,
              const boost::shared_ptr<formic::CoeffObj<S> > & gf)
      : _userinp(userinp),
        _walker(userinp),
        _sl(samp_len),
        _bl(burn_len),
        _init_config(init_config),
        _rng(rng)
      {

        // set the guiding function
        _walker.gf() = gf;

        // burn in the markov chain
        this->burn();

      }

      int sl() const { return _sl; }
      int bl() const { return _bl; }

      /// \brief return the guiding function
      boost::shared_ptr<formic::CoeffObj<S> > gf() { return _walker.gf(); }

      /// \brief return the configuration used by the walker
      const formic::Configuration & config() const { return _walker.config(); }

      /// \brief Add an accumulator
      Sampler & operator<<(const boost::shared_ptr<formic::Accumulator<S> > & acc) {
        // only add the accumulator if it is not already there
        if ( std::find(_walker.accumulators().begin(), _walker.accumulators().end(), acc) == _walker.accumulators().end() )
          _walker.accumulators().push_back(acc);
        return *this;
      }

      /// \brief Add a coefficient object
      Sampler & operator<<(const boost::shared_ptr<formic::CoeffObj<S> > & co) {
        // only add the coefficient object if it is not already there
        if ( _walker.gf() != co && std::find(_walker.co_vec().begin(), _walker.co_vec().end(), co) == _walker.co_vec().end() )
          _walker.co_vec().push_back(co);
        return *this;
      }

      /// \brief take the sample
      void sample(int nsamp = -1) {
        if (nsamp < 0)
          nsamp = _sl;
        _walker.config() = _init_config;
        _walker.lfg() = _rng;
        if ( _userinp.get<bool>("exact_sampling") )
          _walker.exact_sample(_userinp);
        else
          formic::sample(_userinp, nsamp, _walker);
          //formic::sample(_userinp, _sl, _walker, false);
      }

      /// \brief removes any accumulators and coefficient objects other than the guiding function from the walker
      void clear_extras() {
        _walker.accumulators().clear();
        _walker.co_vec().clear();
      }

      formic::fqmc::SimpleSampler<S> simple_sample(int nsamp = -1);

      /// \brief burn in the markov chain
      void burn( int burn_len = -1 ) {
        if ( burn_len < 0 )
          burn_len = _bl;
        if ( burn_len > 0 && !_userinp.get<bool>("exact_sampling") ) {
          _walker.config() = _init_config;
          _walker.lfg() = _rng;
          formic::sample(_userinp, burn_len, _walker);
          _init_config = _walker.config();
          _rng = _walker.lfg();
        }
      }

      /// \brief  update the guiding function
      void set_gf(const boost::shared_ptr<formic::CoeffObj<S> > & gf) {
        _walker.gf() = gf;
        this->burn();
      }
  };

  template<class S> class SimpleSampler {

    // private data members
    private:

      formic::fqmc::Sampler<S> * _s; ///< pointer to the sampler to use

      bool _to_sample; ///< whether to sample upon destruction

      int _nsamp; ///< number of samples to take

    // public member functions
    public:

      /// \brief Construct the sampler
      SimpleSampler(formic::fqmc::Sampler<S> & s, int nsamp) : _s(&s), _to_sample(false), _nsamp(nsamp) {}

      /// \brief Add an object to the sampler
      template<class T>
      SimpleSampler & operator<<(const T & x) {
        *_s << x;
        _to_sample = true;
        return *this;
      }

      /// \brief  Destructor, which takes the sample and clears the sampler's accumulators and co_vec
      ~SimpleSampler() {
        if ( _to_sample ) {
          _s->sample(_nsamp);
          _s->clear_extras();
        }
      }

  };

  /// \brief take the sample
  template<class S> formic::fqmc::SimpleSampler<S> formic::fqmc::Sampler<S>::simple_sample(int nsamp) {
    return formic::fqmc::SimpleSampler<S>(*this, nsamp);
  }

}

}

#endif
